//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.IO;
using System.Text;

namespace OpenLS.Vml.Parsing
{
    internal class SgmlStream : TextReader
    {
        private const int BufferSize = 16384;
        private const int Eof = -1;
        private readonly Decoder _decoder;
        private readonly byte[] _rawBuffer;
        private readonly Stream _stm;
        private char[] _buffer;
        private Encoding _encoding;
        private int _pos;
        private int _rawPos;
        private int _rawUsed;
        private int _used;

        public SgmlStream(Stream stm, Encoding defaultEncoding)
        {
            if (defaultEncoding == null) defaultEncoding = Encoding.UTF8; // default is UTF8
            if (!stm.CanSeek)
            {
                // Need to be able to seek to sniff correctly.
                stm = copyToMemoryStream(stm);
            }
            _stm = stm;
            _rawBuffer = new Byte[BufferSize];
            _rawUsed = stm.Read(_rawBuffer, 0, 4); // maximum byte order mark
            _buffer = new char[BufferSize];

            // Check byte order marks
            _decoder = autoDetectEncoding(_rawBuffer, ref _rawPos, _rawUsed);
            int bom = _rawPos;
            if (_decoder == null)
            {
                _decoder = defaultEncoding.GetDecoder();
                _rawUsed += stm.Read(_rawBuffer, 4, BufferSize - 4);
                decodeBlock();
                // Now sniff to see if there is an XML declaration or HTML <META> tag.
                Decoder sd = sniffEncoding();
                if (sd != null)
                {
                    _decoder = sd;
                }
            }

            // Reset to get ready for Read()
            _stm.Seek(0, SeekOrigin.Begin);
            _pos = _used = 0;
            // skip bom
            if (bom > 0)
            {
                stm.Read(_rawBuffer, 0, bom);
            }
            _rawPos = _rawUsed = 0;
        }

        private static Stream copyToMemoryStream(Stream s)
        {
            const int size = 100000;
            var buffer = new byte[size];
            int len;
            var r = new MemoryStream();
            while ((len = s.Read(buffer, 0, size)) > 0)
            {
                r.Write(buffer, 0, len);
            }
            r.Seek(0, SeekOrigin.Begin);
            s.Close();
            return r;
        }

        private void decodeBlock()
        {
            // shift current chars to beginning.
            if (_pos > 0)
            {
                if (_pos < _used)
                {
                    Array.Copy(_buffer, _pos, _buffer, 0, _used - _pos);
                }
                _used -= _pos;
                _pos = 0;
            }
            int len = _decoder.GetCharCount(_rawBuffer, _rawPos, _rawUsed - _rawPos);
            int available = _buffer.Length - _used;
            if (available < len)
            {
                var newbuf = new char[_buffer.Length + len];
                Array.Copy(_buffer, _pos, newbuf, 0, _used - _pos);
                _buffer = newbuf;
            }
            _used = _pos + _decoder.GetChars(_rawBuffer, _rawPos, _rawUsed - _rawPos, _buffer, _pos);
            _rawPos = _rawUsed; // consumed the whole buffer!
        }

        private static Decoder autoDetectEncoding(byte[] buffer, ref int index, int length)
        {
            if (4 <= (length - index))
            {
                uint w = (uint) buffer[index + 0] << 24 | (uint) buffer[index + 1] << 16 | (uint) buffer[index + 2] << 8 |
                         (uint) buffer[index + 3];
                // see if it's a 4-byte encoding
                switch (w)
                {
                    case 0xfefffeff:
                    case 0x3c000000:
                        throw new NotSupportedException("UCS 4 big endian");
                    case 0xfffefffe:
                    case 0x0000003c:
                        throw new NotSupportedException("UCS 4 little endian");
                }
                w >>= 8;
                if (w == 0xefbbbf)
                {
                    index += 3;
                    return Encoding.UTF8.GetDecoder();
                }
                w >>= 8;
                switch (w)
                {
                    case 0xfeff:
                        index += 2;
                        return Encoding.BigEndianUnicode.GetDecoder();

                    case 0xfffe:
                        index += 2;
                        return new UnicodeEncoding(false, false).GetDecoder();

                    case 0x3c00:
                        goto case 0xfeff;

                    case 0x003c:
                        goto case 0xfffe;
                }
            }
            return null;
        }

        private int readChar()
        {
            // Read only up to end of current buffer then stop.
            if (_pos < _used) return _buffer[_pos++];
            return Eof;
        }

        private int peekChar()
        {
            int ch = readChar();
            if (ch != Eof)
            {
                _pos--;
            }
            return ch;
        }

        private bool sniffPattern(string pattern)
        {
            int ch = peekChar();
            if (ch != pattern[0]) return false;
            for (int i = 0, n = pattern.Length; ch != Eof && i < n; i++)
            {
                ch = readChar();
                char m = pattern[i];
                if (ch != m)
                {
                    return false;
                }
            }
            return true;
        }

        private void sniffWhitespace()
        {
            var ch = (char) peekChar();
            while (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')
            {
                int i = _pos;
                ch = (char) readChar();
                if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n')
                    _pos = i;
            }
        }

        private string sniffLiteral()
        {
            int quoteChar = peekChar();
            if (quoteChar == '\'' || quoteChar == '"')
            {
                readChar(); // consume quote char
                int i = _pos;
                int ch = readChar();
                while (ch != Eof && ch != quoteChar)
                {
                    ch = readChar();
                }
                return (_pos > i) ? new string(_buffer, i, _pos - i - 1) : "";
            }
            return null;
        }

        private string sniffAttribute(string name)
        {
            sniffWhitespace();
            string id = sniffName();
            if (name == id)
            {
                sniffWhitespace();
                if (sniffPattern("="))
                {
                    sniffWhitespace();
                    return sniffLiteral();
                }
            }
            return null;
        }

        private string sniffAttribute(out string name)
        {
            sniffWhitespace();
            name = sniffName();
            if (name != null)
            {
                sniffWhitespace();
                if (sniffPattern("="))
                {
                    sniffWhitespace();
                    return sniffLiteral();
                }
            }
            return null;
        }

        private void sniffTerminator(string term)
        {
            int ch = readChar();
            int i = 0;
            int n = term.Length;
            while (i < n && ch != Eof)
            {
                if (term[i] == ch)
                {
                    i++;
                    if (i == n) break;
                }
                else
                {
                    i = 0; // reset.
                }
                ch = readChar();
            }
        }

        private Decoder sniffEncoding()
        {
            if (sniffPattern("<?xml"))
            {
                string version = sniffAttribute("version");
                if (version != null)
                {
                    string encoding = sniffAttribute("encoding");
                    if (encoding != null)
                    {
                        try
                        {
                            Encoding enc = Encoding.GetEncoding(encoding);
                            if (enc != null)
                            {
                                _encoding = enc;
                                return enc.GetDecoder();
                            }
                        }
                        catch (Exception)
                        {
                            // oh well then.
                        }
                    }
                    sniffTerminator(">");
                }
            }
            if (true)
            {
                return sniffMeta();
            }
        }

        private Decoder sniffMeta()
        {
            int i = readChar();
            while (i != Eof)
            {
                var ch = (char) i;
                if (ch == '<')
                {
                    string name = sniffName();
                    if (name != null && StringUtilities.EqualsIgnoreCase(name, "meta"))
                    {
                        string httpequiv = null;
                        string content = null;
                        while (true)
                        {
                            string value = sniffAttribute(out name);
                            if (name == null)
                            {
                                break;
                            }
                            if (StringUtilities.EqualsIgnoreCase(name, "http-equiv"))
                            {
                                httpequiv = value;
                            }
                            else if (StringUtilities.EqualsIgnoreCase(name, "content"))
                            {
                                content = value;
                            }
                        }
                        if (httpequiv != null && StringUtilities.EqualsIgnoreCase(httpequiv, "content-type") &&
                            content != null)
                        {
                            int j = content.IndexOf("charset");
                            if (j >= 0)
                            {
                                //charset=utf-8
                                j = content.IndexOf("=", j);
                                if (j >= 0)
                                {
                                    j++;
                                    int k = content.IndexOf(";", j);
                                    if (k < 0) k = content.Length;
                                    string charset = content.Substring(j, k - j).Trim();
                                    try
                                    {
                                        Encoding e = Encoding.GetEncoding(charset);
                                        _encoding = e;
                                        return e.GetDecoder();
                                    }
                                    catch
                                    {
                                    }
                                }
                            }
                        }
                    }
                }
                i = readChar();
            }
            return null;
        }

        private string sniffName()
        {
            int c = peekChar();
            if (c == Eof)
                return null;
            var ch = (char) c;
            int start = _pos;
            while (_pos < _used - 1 && (Char.IsLetterOrDigit(ch) || ch == '-' || ch == '_' || ch == ':'))
            {
                ch = _buffer[++_pos];
            }
            if (start == _pos) return null;
            return new string(_buffer, start, _pos - start);
        }

        public override int Peek()
        {
            int result = Read();
            if (result != Eof)
            {
                _pos--;
            }
            return result;
        }

        public override int Read()
        {
            if (_pos == _used)
            {
                _rawUsed = _stm.Read(_rawBuffer, 0, _rawBuffer.Length);
                _rawPos = 0;
                if (_rawUsed == 0) return Eof;
                decodeBlock();
            }
            if (_pos < _used) return _buffer[_pos++];
            return -1;
        }

        public override int Read(char[] buffer, int start, int length)
        {
            if (_pos == _used)
            {
                _rawUsed = _stm.Read(_rawBuffer, 0, _rawBuffer.Length);
                _rawPos = 0;
                if (_rawUsed == 0) return -1;
                decodeBlock();
            }
            if (_pos < _used)
            {
                length = Math.Min(_used - _pos, length);
                Array.Copy(_buffer, _pos, buffer, start, length);
                _pos += length;
                return length;
            }
            return 0;
        }

        public override int ReadBlock(char[] buffer, int index, int count)
        {
            return Read(buffer, index, count);
        }

        // Read up to end of line, or full buffer, whichever comes first.

        public override string ReadToEnd()
        {
            var buffer = new char[100000]; // large block heap is more efficient
            int len;
            var sb = new StringBuilder();
            while ((len = Read(buffer, 0, buffer.Length)) > 0)
            {
                sb.Append(buffer, 0, len);
            }
            return sb.ToString();
        }

        public override void Close()
        {
            _stm.Close();
        }
    }
}